home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1994…tember: Reference Library / Dev.CD Sep 94.toast / Periodicals / develop / develop Issue 6 / develop 6 code / TCP / NewsWatcher / NewsWatcher 2.0d15 source / source / full.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-08-30  |  21.4 KB  |  806 lines  |  [TEXT/KAHL]

  1. /*----------------------------------------------------------------------------
  2.  
  3.     full.c
  4.  
  5.     This module handles tasks involving the full group list.
  6.     
  7.     Portions copyright © 1990, Apple Computer.
  8.     Portions copyright © 1993, Northwestern University.
  9.  
  10. ----------------------------------------------------------------------------*/
  11.  
  12. #include <string.h>
  13. #include <stdio.h>
  14.  
  15. #include "glob.h"
  16. #include "full.h"
  17. #include "util.h"
  18. #include "qsort.h"
  19. #include "dlgutil.h"
  20. #include "open.h"
  21. #include "resize.h"
  22. #include "wind.h"
  23. #include "close.h"
  24. #include "nntp.h"
  25.  
  26.  
  27.  
  28. #define kDeletedGroupsDlg                142            /* Deleted groups dialog */
  29. #define kDeletedGroupsTheInfoItem        3
  30.  
  31.  
  32.  
  33. static short    gNumAllocGroups;    /* number of groups allocated in full group list */
  34. static long        gGroupNamesSize;    /* current size of gGroupNames full group array */
  35. static long        gGroupNamesNext;    /* next available offset in gGroupNames */
  36.  
  37. static TGroup    **gNewGroups;        /* handle to new group array */
  38. static short    gNumNew = 0;        /* number of new groups */
  39. static short     gNumNewAllocated;    /* number of new groups allocated in gNewGroups */
  40.  
  41. static Handle    gDeleted;            /* handle to list of deleted groups */
  42. static long        gDeletedLen;        /* length of list of deleted groups */
  43. static long        gDeletedAllocated;    /* number of bytes allocated for gDeleted */
  44.  
  45.  
  46.  
  47. /*----------------------------------------------------------------------------
  48.     FindGroupIndex 
  49.     
  50.     Finds the index of a group in the full group array gGroupArray.
  51.  
  52.     Entry:    name = group name.
  53.             gGroupArray sorted in increasing order by group name.
  54.  
  55.     Exit:    function result = index in gGroupArray of group, or -1 if not found.
  56. ----------------------------------------------------------------------------*/
  57.  
  58. short FindGroupIndex (const char *name)
  59. {
  60.     long low = 0;
  61.     long high = gNumGroups-1;
  62.     long mid;
  63.     short compare;
  64.     
  65.     while (low <= high) {
  66.         mid = (low + high) >> 1;
  67.         compare = strcmp(name, *gGroupNames + (*gGroupArray)[mid].nameOffset);
  68.         if (compare == 0) {
  69.             return mid;
  70.         } else if (compare < 0) {
  71.             high = mid-1;
  72.         } else {
  73.             low = mid+1;
  74.         }
  75.     }
  76.     return -1;        
  77. }
  78.  
  79.  
  80.  
  81. /*----------------------------------------------------------------------------
  82.     StoreOneGroup 
  83.     
  84.     Appends a new group to the end of a group array.
  85.  
  86.     Entry:    group = group name.
  87.             groupArray = handle to group array.
  88.             *numGroups = number of groups in group array.
  89.             *numGroupsAllocated = number of groups allocated in group array.
  90. ----------------------------------------------------------------------------*/
  91.  
  92. static void StoreOneGroup (const char *group, TGroup **groupArray,
  93.     short *numGroups, short *numGroupsAllocated)    
  94. {
  95.     TGroup newGroup;
  96.     short nameLen;
  97.  
  98.     /* Set most fields of the group record to default values */
  99.     
  100.     newGroup.firstMess = newGroup.lastMess = 0;
  101.     newGroup.numUnread = 0;
  102.     newGroup.status = 'y';
  103.     newGroup.unread = nil;
  104.     newGroup.onlyRedrawCount = false;
  105.  
  106.     /* Append the new group's name to the end of the global groupname list */
  107.     
  108.     nameLen = strlen(group) + 1;
  109.     if (gGroupNamesNext + nameLen > gGroupNamesSize) {
  110.         gGroupNamesSize += 10000;
  111.         MySetHandleSize(gGroupNames, gGroupNamesSize);
  112.     }
  113.     BlockMove(group, *gGroupNames + gGroupNamesNext, nameLen);
  114.     newGroup.nameOffset = gGroupNamesNext;
  115.     if (nameLen > 256) {
  116.         nameLen = 256;
  117.         *(*gGroupNames + gGroupNamesNext + 255) = 0;
  118.     }
  119.     gGroupNamesNext += nameLen;
  120.  
  121.     /* Store the new group record in the group array */
  122.     
  123.     if (*numGroups >= *numGroupsAllocated) {
  124.         (*numGroupsAllocated) += 500;
  125.         MySetHandleSize((Handle)groupArray, (*numGroupsAllocated)*sizeof(TGroup));
  126.     }
  127.     BlockMove(&newGroup, *groupArray + *numGroups, sizeof(TGroup));
  128.     (*numGroups)++;
  129. }
  130.  
  131.  
  132.  
  133. /*----------------------------------------------------------------------------
  134.     GroupCompare 
  135.     
  136.     A comparison routine used in the calls to fastqsort() in 
  137.     SortFullGroupArray and CheckForNewGroups below. It does a 
  138.     simple string compare and gives time to background applications.
  139. ----------------------------------------------------------------------------*/
  140.  
  141. static short GroupCompare (void *one, void *two)
  142. {
  143.     static short Counter = 0;
  144.  
  145.     if((++Counter & 0x1f) == 0) {
  146.         GiveTime();
  147.         Counter = 0;
  148.     }
  149.  
  150.     return strcmp(*gGroupNames + ((TGroup*)one)->nameOffset, 
  151.         *gGroupNames + ((TGroup*)two)->nameOffset);
  152. }
  153.  
  154.  
  155.  
  156. /*----------------------------------------------------------------------------
  157.     SortFullGroupArray 
  158.     
  159.     Sorts the full group list alphabetically by group name.
  160.     
  161.     Exit:    function result = true if list sorted, false if canceled.
  162. ----------------------------------------------------------------------------*/
  163.  
  164. static Boolean SortFullGroupArray()
  165. {
  166.     Boolean result = true;
  167.  
  168.     if (gNumGroups) {
  169.         StatusWindow("Sorting full group list.");
  170.         HLock((Handle)gGroupArray);
  171.         result = FastQSort(*gGroupArray, gNumGroups, sizeof(TGroup), GroupCompare);
  172.         HUnlock((Handle)gGroupArray);
  173.     }
  174.     return result;
  175. }
  176.  
  177.  
  178.  
  179. /*----------------------------------------------------------------------------
  180.     ReadGroupsFromPrefs 
  181.     
  182.     Reads in the full group list stored in the preferences file.
  183.     
  184.     Entry:    fRefNum = refnum of opened prefs file.
  185.  
  186.     Exit:    function result = true if no errors, false if error or canceled.
  187. ----------------------------------------------------------------------------*/
  188.  
  189. Boolean ReadGroupsFromPrefs (short fRefNum)
  190. {
  191.     OSErr err;
  192.     long logEOF;
  193.     Boolean needSort = false;
  194.     char *p, *pEnd;
  195.     char *prevName = nil;
  196.     char *curName;
  197.     TGroup *g;
  198.  
  199.     StatusWindow("Reading full group list.");    
  200.  
  201.     /* Read the saved group names. */
  202.     
  203.     if ((err = GetEOF(fRefNum, &logEOF)) != noErr) goto exit;
  204.     gGroupNamesSize = gGroupNamesNext = logEOF - sizeof(TPrefRec);
  205.     gGroupNames = MyNewHandle(gGroupNamesSize);
  206.     HLock(gGroupNames);
  207.     if ((err = FSRead(fRefNum, &gGroupNamesSize, *gGroupNames)) != noErr) goto exit;
  208.     HUnlock(gGroupNames);
  209.     
  210.     /* Check to make certain the last group name ends in CR. If not, strip
  211.        any trailing junk. */
  212.        
  213.     p = *gGroupNames + gGroupNamesSize - 1;
  214.     while (p >= *gGroupNames && *p != CR) p--;
  215.     p++;
  216.     if (p < *gGroupNames + gGroupNamesSize) {
  217.         gGroupNamesSize = gGroupNamesNext = p - *gGroupNames;
  218.         MySetHandleSize(gGroupNames, gGroupNamesSize);
  219.         if (p == *gGroupNames) return true;
  220.     }
  221.     
  222.     /* Walk through the gGroupNames buffer. Count the number of groups. 
  223.        Change all CR to 0. Check to see if the groups are alread sorted
  224.        (they should be). */
  225.        
  226.     HLock(gGroupNames);
  227.     p = *gGroupNames;
  228.     pEnd = p + gGroupNamesSize;
  229.     while (p < pEnd) {
  230.         curName = p;
  231.         while (*p != CR) p++;
  232.         *p++ = 0;
  233.         gNumGroups++;
  234.         needSort = needSort || (prevName != nil && strcmp(prevName, curName) > 0);
  235.         prevName = curName;
  236.         if ((gNumGroups & 0x1f) == 0 && !GiveTime()) {
  237.             err = -1;
  238.             goto exit;
  239.         }
  240.     }
  241.     HUnlock(gGroupNames);
  242.     
  243.     /* Allocate and initialize the full group array. */
  244.     
  245.     gGroupArray = (TGroup**)MyNewHandle(gNumGroups*sizeof(TGroup));
  246.     p = *gGroupNames;
  247.     pEnd = p + gGroupNamesSize;
  248.     g = *gGroupArray;
  249.     while (p < pEnd) {
  250.         g->nameOffset = p - *gGroupNames;
  251.         while (*p != 0) p++;
  252.         p++;
  253.         g++;
  254.     }
  255.     
  256.     /* If necessary, sort the full group array. */
  257.     
  258.     if (needSort && !SortFullGroupArray()) goto exit2;
  259.     
  260.     return true;
  261.     
  262. exit:
  263.  
  264.     UnexpectedErrorMessage(err);
  265.     
  266. exit2:
  267.  
  268.     if (gGroupNames != nil) HUnlock(gGroupNames);
  269.     gNumGroups = 0;
  270.     return false;
  271. }
  272.  
  273.  
  274.  
  275. /*----------------------------------------------------------------------------
  276.     MakeGroupList 
  277.     
  278.     Creates a List Manager list for a group window.
  279.  
  280.     Entry:    numGroups = number of groups in list.
  281.             theList = handle to list record.
  282.  
  283.     Exit:    Any previous list deleted. A new list is created with numGroups
  284.             cells. The cell data is initialized to 0, 1, 2, ..., numGroups-1.
  285. ----------------------------------------------------------------------------*/
  286.  
  287. void MakeGroupList (short numGroups, ListHandle theList)
  288. {
  289.     short i;
  290.     short *pCells;
  291.     short *pCellArray;
  292.     short offset;
  293.     
  294.     LDoDraw(false,theList);
  295.     LDelRow(0, 0, theList);
  296.     LAddRow(numGroups,0,theList);
  297.  
  298.     MySetHandleSize((**theList).cells, 2*numGroups);
  299.     pCells = (short*)*((**theList).cells);
  300.     pCellArray = (**theList).cellArray;
  301.     offset = 0;
  302.  
  303.     for (i=0; i<numGroups; i++) {
  304.         *pCellArray++ = offset;
  305.         *pCells++ = i;
  306.         offset += 2;
  307.     }
  308.     *pCellArray = offset;
  309.     LDoDraw(true,theList);
  310. }
  311.  
  312.  
  313.  
  314. /*----------------------------------------------------------------------------
  315.     CreateNewGroupListWindow 
  316.     
  317.     Creates a New Groups window containing the names of the new newsgroups.
  318. ----------------------------------------------------------------------------*/
  319.  
  320. void CreateNewGroupListWindow (void)
  321. {
  322.     TWindow **info;
  323.     WindowPtr theWind;
  324.     GrafPtr savePort;
  325.     Cell theCell;
  326.     Point firstOffset;
  327.     
  328.     if (gNumNew == 0) return;
  329.     
  330.     SetPt(&firstOffset,kOffLeft,kOffTop);
  331.     
  332.     info = (TWindow**) GetWRefCon(theWind = 
  333.         MakeNewWindow(kNewGroup,firstOffset,"\pNew Groups"));
  334.     
  335.     (**info).numGroups = gNumNew;
  336.     (**info).groupArray = gNewGroups;
  337.     
  338.     MakeGroupList(gNumNew, (**info).theList);
  339.     
  340.     SetPt(&theCell,0,0);
  341.     LSetSelect(true,theCell,(**info).theList);
  342.     
  343.     DoZoom(theWind, inZoomOut);
  344.     ShowWindow(theWind);
  345.     
  346.     GetPort(&savePort);
  347.     SetPort(theWind);
  348.     InvalRect(&theWind->portRect);
  349.     SetPort(savePort);
  350. }
  351.  
  352.  
  353.  
  354. /*----------------------------------------------------------------------------
  355.     CreateFullGroupListWindow 
  356.     
  357.     Creates the full group list window.
  358.  
  359.     Exit:    function result = true if window created, false if error.
  360. ----------------------------------------------------------------------------*/
  361.  
  362. Boolean CreateFullGroupListWindow (void)
  363. {
  364.     WindowPtr wind;
  365.     TWindow **info;
  366.     GrafPtr savePort;
  367.     Point firstOffset;
  368.     Point thePt;
  369.     WStateData **stateHndl;
  370.  
  371.     SetPt(&firstOffset,kOffLeft,kOffTop);
  372.     info = (TWindow**) GetWRefCon(gFullGroupWindow = wind =
  373.                 MakeNewWindow(kFullGroup,firstOffset,"\pFull Group List"));
  374.  
  375.     (**info).numGroups = gNumGroups;
  376.     (**info).groupArray = gGroupArray;
  377.     
  378.     MakeGroupList(gNumGroups, (**info).theList);
  379.     
  380.     SetPt(&thePt,0,0);
  381.     LSetSelect(true,thePt,(**info).theList);
  382.     
  383.     GetPort(&savePort);
  384.     SetPort(wind);
  385.  
  386.     SizeWindow(wind,
  387.         gPrefs.groupWindowRect.right-gPrefs.groupWindowRect.left,
  388.         gPrefs.groupWindowRect.bottom-gPrefs.groupWindowRect.top,
  389.         true);
  390.     SizeContents(wind);
  391.     MoveWindow(wind,
  392.         gPrefs.groupWindowRect.left,
  393.         gPrefs.groupWindowRect.top,
  394.         false);
  395.     stateHndl = (WStateData **) ((WindowPeek)wind)->dataHandle;
  396.     (**stateHndl).userState = gPrefs.groupWindowRect;
  397.  
  398.     if (!gPrefs.groupWindowVisible) {
  399.         gPrefs.groupWindowVisible = true;
  400.         ShowHideGroups();
  401.         gMustDoZoomOnShowFullGroupList = true;
  402.     } else {
  403.         if (!DoZoom(wind,inZoomOut)) return false;
  404.         ShowWindow(wind);
  405.         gMustDoZoomOnShowFullGroupList = false;
  406.     }
  407.  
  408.     InvalRect(&wind->portRect);
  409.     SetPort(savePort);
  410.     
  411.     return true;
  412. }    
  413.  
  414.  
  415.  
  416. /*----------------------------------------------------------------------------
  417.     AdjustFullGroupListChildWindows
  418.  
  419.     This function must be called whenever the full group list changes. It locates
  420.     all the open child subject list windows. If a group has been deleted, any
  421.     associated open child subject list window is closed. If a group still exists,
  422.     the "parentGroup" backpointer in the child window's TWindow info is adjusted
  423.     to point to the new location of the parent group in the full group array
  424.     gGroupArray.
  425. ----------------------------------------------------------------------------*/
  426.  
  427. static void AdjustFullGroupListChildWindows (void)
  428. {
  429.     TWindow        **info, **childInfo;
  430.     TChild        **childList, **prevChildList;
  431.     WindowPtr    childWindow;
  432.     CStr255        groupName;
  433.     short        index;
  434.     
  435.     gFullGroupListDirty = true;
  436.     if (gFullGroupWindow == nil) return;
  437.     info = (TWindow**)GetWRefCon(gFullGroupWindow);
  438.     childList = (**info).childList;
  439.     prevChildList = nil;
  440.     while (childList != nil) {
  441.         childWindow = (**childList).childWindow;
  442.         childInfo = (TWindow**)GetWRefCon(childWindow);
  443.         strcpy(groupName, *gGroupNames + (**childInfo).groupNameOffset);
  444.         index = FindGroupIndex(groupName);
  445.         if (index == -1) {
  446.             /* Group has been deleted. Close the child window. */
  447.             DoCloseWindow(childWindow);
  448.             if (prevChildList == nil) {
  449.                 childList = (**info).childList;
  450.             } else {
  451.                 childList = (**prevChildList).next;
  452.             }
  453.         } else {
  454.             /* Group still exists. Update the parentGroup backpointer. */
  455.             (**childInfo).parentGroup = index;
  456.             prevChildList = childList;
  457.             childList = (**childList).next;
  458.         }
  459.     }
  460. }
  461.  
  462.  
  463. /*----------------------------------------------------------------------------
  464.     UpdateFullGroupWindow 
  465.     
  466.     Makes sure that the Full Group List window corresponds to the changed 
  467.     full group list. It must be called whenever the full group list changes.
  468. ----------------------------------------------------------------------------*/
  469.  
  470. static void UpdateFullGroupWindow (void)
  471. {
  472.     TWindow **info;
  473.     Point thePt;
  474.     GrafPtr savePort;
  475.  
  476.     AdjustFullGroupListChildWindows();
  477.     if (gFullGroupWindow) {
  478.         info = (TWindow**)GetWRefCon(gFullGroupWindow);
  479.         (**info).groupArray = gGroupArray;
  480.         (**info).numGroups = gNumGroups;
  481.         MakeGroupList(gNumGroups, (**info).theList);
  482.         SetPt(&thePt,0,0);
  483.         LSetSelect(true,thePt,(**info).theList);
  484.         GetPort(&savePort);
  485.         SetPort(gFullGroupWindow);
  486.         SizeContents(gFullGroupWindow);
  487.         InvalRect(&gFullGroupWindow->portRect);
  488.         SetPort(savePort);
  489.     }
  490. }
  491.  
  492.  
  493.  
  494. /*----------------------------------------------------------------------------
  495.     NewGroupsFunc
  496.  
  497.     When we're checking for new groups, GetGroupNames calls this function
  498.     once for every group that the server returns. The new groups are
  499.     accumulated in the gNewGroups group array.
  500.  
  501.     Entry:    name = the name of the potential new group.
  502.  
  503.     Exit:    function result = true if no errors, false if error or cancelled.
  504. ----------------------------------------------------------------------------*/
  505.  
  506. static Boolean NewGroupsFunc(const char *name)
  507. {
  508.     short nameWidth;
  509.  
  510.     if (!GiveTime()) return false;
  511.     if (FindGroupIndex(name) == -1) {
  512.         StoreOneGroup(name, gNewGroups, &gNumNew, &gNumNewAllocated);
  513.         if (gPrefs.maxGroupNameWidth > 0) {
  514.             nameWidth = TextWidth(name, 0, strlen(name));
  515.             if (nameWidth > gPrefs.maxGroupNameWidth) gPrefs.maxGroupNameWidth = nameWidth;
  516.         }
  517.     }
  518.     return true;
  519. }
  520.  
  521.  
  522.  
  523. /*----------------------------------------------------------------------------
  524.     MergeNewGroupsIntoFullGroupList 
  525.     
  526.     Merges new groups into the full group list. Both lists must be sorted on 
  527.     entry. The full group list remains sorted on exit.
  528. ----------------------------------------------------------------------------*/
  529.  
  530. static void MergeNewGroupsIntoFullGroupList (void)
  531. {
  532.     short numLeftToInsert, numToMoveUp;
  533.     TGroup *fullListPtr, *newListPtr;
  534.     char *newName;
  535.  
  536.     gNumGroups += gNumNew;
  537.     gNumAllocGroups = gNumGroups;
  538.     MySetHandleSize((Handle)gGroupArray, gNumAllocGroups*sizeof(TGroup));
  539.     
  540.     numLeftToInsert = gNumNew;
  541.     fullListPtr = *gGroupArray + gNumGroups - gNumNew - 1;
  542.     newListPtr = *gNewGroups + gNumNew - 1;
  543.     while (numLeftToInsert > 0) {
  544.         newName = *gGroupNames + newListPtr->nameOffset;
  545.         numToMoveUp = 0;
  546.         while (fullListPtr >= *gGroupArray &&
  547.             strcmp(newName, *gGroupNames + fullListPtr->nameOffset) <= 0) 
  548.         {
  549.             fullListPtr--;
  550.             numToMoveUp++;
  551.         }
  552.         if (numToMoveUp > 0)
  553.             BlockMove(fullListPtr + 1, fullListPtr + numLeftToInsert + 1, 
  554.                 numToMoveUp*sizeof(TGroup));
  555.         BlockMove(newListPtr, fullListPtr + numLeftToInsert, sizeof(TGroup));
  556.         newListPtr--;
  557.         numLeftToInsert--;
  558.     }
  559. }
  560.  
  561.  
  562.  
  563. /*----------------------------------------------------------------------------
  564.     CheckForNewGroups 
  565.     
  566.     Asks the NNTP server for any new groups created since last time we checked.
  567.  
  568.     Exit:    function result = true if no errors, false if error or canceled.
  569. ----------------------------------------------------------------------------*/
  570.  
  571. Boolean CheckForNewGroups (void)
  572. {
  573.     StatusWindow("Checking for new groups.");
  574.  
  575.     gNewGroups = (TGroup**)MyNewHandle(100*sizeof(TGroup));
  576.     gNumNewAllocated = 100;
  577.     gNumNew = 0;
  578.  
  579.     /* Ask NNTP server for the new group names. */
  580.     
  581.     SetPort(gFullGroupWindow);
  582.     if (!GetGroupNames(gPrefs.groupCheckTime, NewGroupsFunc)) goto exit;
  583.  
  584.     if (gNumNew > 0) {
  585.     
  586.         /* Shrink the gNewGroups and gGroupNames arrays. */
  587.         
  588.         MySetHandleSize((Handle)gNewGroups, gNumNew*sizeof(TGroup));
  589.         gGroupNamesSize = gGroupNamesNext;
  590.         MySetHandleSize(gGroupNames, gGroupNamesNext);
  591.         
  592.         /* Sort the gNewGroups array. */
  593.         
  594.         HLock((Handle)gNewGroups);
  595.         if (!FastQSort(*gNewGroups, gNumNew, sizeof(TGroup), GroupCompare)) goto exit;
  596.         HUnlock((Handle)gNewGroups);
  597.         
  598.         /* Merge the new groups into the full group list. */
  599.  
  600.         MergeNewGroupsIntoFullGroupList();
  601.         UpdateFullGroupWindow();
  602.         
  603.     } else {
  604.     
  605.         MyDisposHandle((Handle)gNewGroups);
  606.         
  607.     }
  608.  
  609.     /* Record the time new groups were checked for */
  610.     
  611.     GetDateTime(&gPrefs.groupCheckTime);
  612.  
  613.     return true;
  614.     
  615. exit:
  616.     MyDisposHandle((Handle)gNewGroups);
  617.     gNumNew = 0;
  618.     return false;
  619. }
  620.  
  621.  
  622.  
  623. /*----------------------------------------------------------------------------
  624.     DelGroupsFunc
  625.  
  626.     When we're checking for deleted groups, GetGroupNames calls this function
  627.     once for every group in the server's full group list. It changes the group's 
  628.     status to ' ' to mark it as still present.
  629.  
  630.     Entry:    name = group name.
  631.  
  632.     Exit:    function result = true if no errors, false if error or canceled.
  633. ----------------------------------------------------------------------------*/
  634.  
  635. static Boolean DelGroupsFunc (const char *name)
  636. {
  637.     short index;
  638.  
  639.     if (!GiveTime()) return false;
  640.     index = FindGroupIndex(name);
  641.     if (index != -1) (*gGroupArray)[index].status = ' ';
  642.     return true;
  643. }
  644.  
  645.  
  646.  
  647. /*----------------------------------------------------------------------------
  648.     CheckForDeletedGroups 
  649.     
  650.     Fetches the entire group list from the server and uses it to see if any 
  651.     of the groups in the full group list have been deleted
  652. ----------------------------------------------------------------------------*/
  653.  
  654. void CheckForDeletedGroups (void)
  655. {
  656.     short i, numDel, numToMoveDown;
  657.     TGroup *prev, *cur, *curEnd;
  658.     char *name;
  659.     short nameWidth;
  660.     long len;
  661.     DialogPtr dlg;
  662.     short fontNum;
  663.     short item;
  664.  
  665.     StatusWindow("Checking for deleted groups.");
  666.  
  667.     /* Mark all groups as deleted, then see which ones really exist */
  668.  
  669.     for (i = 0; i < gNumGroups; i++) (*gGroupArray)[i].status = 'x';
  670.     if (!GetGroupNames(0, DelGroupsFunc)) return;
  671.  
  672.     /* Delete any groups still marked as deleted. */
  673.     
  674.     gDeleted = MyNewHandle(1000);
  675.     gDeletedLen = 0;
  676.     gDeletedAllocated = 1000;
  677.     
  678.     SetPort(gFullGroupWindow);
  679.     numDel = 0;
  680.     HLock((Handle)gGroupArray);
  681.     HLock(gGroupNames);
  682.     prev = *gGroupArray;
  683.     cur = *gGroupArray;
  684.     curEnd = *gGroupArray + gNumGroups;
  685.     while (cur < curEnd) {
  686.         numToMoveDown = 0;
  687.         while (cur < curEnd && cur->status != 'x') {
  688.             cur++;
  689.             numToMoveDown++;
  690.         }
  691.         if (numDel > 0 && numToMoveDown > 0)
  692.             BlockMove(prev + numDel, prev, numToMoveDown*sizeof(TGroup));
  693.         prev += numToMoveDown;
  694.         if (cur < curEnd) {
  695.             numDel++;
  696.             name = *gGroupNames + cur->nameOffset;
  697.             len = strlen(name) + 1;
  698.             if (gDeletedLen + len > gDeletedAllocated) {
  699.                 gDeletedAllocated += 1000;
  700.                 MySetHandleSize(gDeleted, gDeletedAllocated);
  701.             }
  702.             BlockMove(name, *gDeleted + gDeletedLen, len);
  703.             gDeletedLen += len;
  704.             (*gDeleted)[gDeletedLen - 1] = CR;
  705.             if (gPrefs.maxGroupNameWidth > 0) {
  706.                 nameWidth = TextWidth(name, 0, strlen(name));
  707.                 if (nameWidth >= gPrefs.maxGroupNameWidth) gPrefs.maxGroupNameWidth = 0;
  708.             }
  709.         }
  710.         cur++;
  711.     }
  712.     HUnlock((Handle)gGroupArray);
  713.     HUnlock(gGroupNames);
  714.  
  715.     if (numDel > 0) {
  716.         gNumGroups -= numDel;
  717.         gNumAllocGroups = gNumGroups;
  718.         MySetHandleSize((Handle)gGroupArray, gNumGroups*sizeof(TGroup));
  719.         UpdateFullGroupWindow();
  720.         dlg = MyGetNewDialog(kDeletedGroupsDlg);
  721.         GetFNum("\pMonaco", &fontNum);
  722.         MySetHandleSize(gDeleted, gDeletedLen - 1);
  723.         SetItemReadOnly(dlg, kDeletedGroupsTheInfoItem, gDeleted, fontNum, 9);
  724.         MyModalDialog(DialogFilter, &item, false, true);
  725.         MyDisposDialog(dlg);
  726.     } else {
  727.         ErrorMessage("No deleted groups.");
  728.     }
  729.     MyDisposHandle(gDeleted);
  730. }
  731.  
  732.  
  733.  
  734. /*----------------------------------------------------------------------------
  735.     ReadGroupsFunc
  736.  
  737.     When we're reading the full group list from the server, GetGroupNames calls 
  738.     this function once for every group that the server returns. The new groups
  739.     are appended to the end of the full group array gGroupArray.
  740.  
  741.     Entry:    name = group name.
  742.  
  743.     Exit:    function result = true if no errors, false if error or canceled.
  744. ----------------------------------------------------------------------------*/
  745.  
  746. static Boolean ReadGroupsFunc (const char *name)
  747. {
  748.     if (!GiveTime()) return false;
  749.     StoreOneGroup(name, gGroupArray, &gNumGroups, &gNumAllocGroups);
  750.     return true;
  751. }
  752.  
  753.  
  754.  
  755. /*----------------------------------------------------------------------------
  756.     ReadGroupsFromServer 
  757.     
  758.     Fetches the entire group list from the server the very first time the 
  759.     program is run (no Prefs file found). It also processes the "Rebuild 
  760.     Full Group List" command.
  761.  
  762.     Exit:    function result = true if no errors, false if error or cancelled.
  763. ----------------------------------------------------------------------------*/
  764.  
  765. Boolean ReadGroupsFromServer (void)
  766. {
  767.     TGroup** savedGroupArray = nil;
  768.     short savedNumGroups;
  769.     short savedNumAllocGroups;
  770.  
  771.     StatusWindow("Getting full group list from server.");
  772.  
  773.     if (gGroupArray != nil) {
  774.         savedGroupArray = gGroupArray;
  775.         savedNumGroups = gNumGroups;
  776.         savedNumAllocGroups = gNumAllocGroups;
  777.     }
  778.     gNumGroups = 0;
  779.     gGroupArray = (TGroup**)MyNewHandle(2000*sizeof(TGroup));
  780.     gNumAllocGroups = 2000;
  781.     if (gGroupNames == nil) {
  782.         gGroupNames = MyNewHandle(40000);
  783.         gGroupNamesSize = 40000;
  784.         gGroupNamesNext = 0;
  785.     }
  786.     if (!GetGroupNames(0, ReadGroupsFunc)) goto exit;
  787.     gNumAllocGroups = gNumGroups;
  788.     MySetHandleSize((Handle)gGroupArray, gNumGroups*sizeof(TGroup));
  789.     gGroupNamesSize = gGroupNamesNext;
  790.     MySetHandleSize(gGroupNames, gGroupNamesSize);
  791.     if (!SortFullGroupArray()) goto exit;
  792.     gPrefs.maxGroupNameWidth = 0;
  793.     UpdateFullGroupWindow();
  794.     GetDateTime(&gPrefs.groupCheckTime);
  795.     MyDisposHandle((Handle)savedGroupArray);
  796.     return true;
  797.     
  798. exit:
  799.     if (savedGroupArray != nil) {
  800.         MyDisposHandle((Handle)gGroupArray);
  801.         gGroupArray = savedGroupArray;
  802.         gNumGroups = savedNumGroups;
  803.         gNumAllocGroups = savedNumAllocGroups;
  804.     }
  805.     return false;
  806. }